﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using FFmpegWrappedSharpRecorder.Models;

// ReSharper disable once IdentifierTypo
namespace FFmpegWrappedSharpRecorder.Tools
{
    // ReSharper disable once IdentifierTypo
    internal static class FFmpegHelper
    {
        public static string? VideoPath { get; private set; }
        private static Process? _process;

        /// <summary>
        /// 获取设备列表
        /// </summary>
        /// <param name="ffmpegPath">FFmpeg路径</param>
        /// <returns>设备列表信息</returns>
        // ReSharper disable once IdentifierTypo
        public static Tuple<bool, List<DeviceInfo>, string> GetDeviceList(string ffmpegPath)
        {
            var workingDirectory = Path.GetDirectoryName(ffmpegPath) ?? string.Empty;
            var cmd = @"-list_devices true -f dshow -i dummy";
            var ret = ProcessHelper.Execute(
                ffmpegPath,
                workingDirectory,
                cmd,
                Encoding.UTF8,
                redirectStandardOutput: false,
                redirectStandardError: true,
                getOutputStream: false,
                getErrorStream: true,
                createNoWindow: true
            );
            if (!ret.Item1)
            {
                return new Tuple<bool, List<DeviceInfo>, string>(false, new List<DeviceInfo>(), ret.Item3);
            }

            try
            {
                //[dshow @ 0000018b1a24c740] "USB2.0 HD UVC WebCam" (video)
                //[dshow @ 0000018b1a24c740] "麦克风阵列 (Realtek(R) Audio)" (audio)
                var deviceList = ret.Item2.Item2
                    .Where(t => t.StartsWith("[dshow") && (t.Contains("(video)") || t.Contains("(audio)")))
                    .Select(t =>
                    {
                        try
                        {
                            var value1 = "] \"";
                            var value2 = "\" (";
                            var index1 = t.IndexOf(value1, StringComparison.CurrentCulture) + value1.Length;
                            var index2 = t.IndexOf(value2, StringComparison.CurrentCulture);
                            var deviceName = t.Substring(index1, index2 - index1);
                            if (t.Contains("(video)"))
                            {
                                return new DeviceInfo(DeviceType.Video, deviceName);
                            }

                            if (t.Contains("(audio)"))
                            {
                                return new DeviceInfo(DeviceType.Audio, deviceName);
                            }
                        }
                        catch
                        {
                            //Do nothing
                        }

                        return new DeviceInfo(DeviceType.Unknown, t);
                    })
                    .ToList();
                return new Tuple<bool, List<DeviceInfo>, string>(true, deviceList, string.Empty);
            }
            catch (Exception e)
            {
                return new Tuple<bool, List<DeviceInfo>, string>(false, new List<DeviceInfo>(), e.Message);
            }
        }

        /// <summary>
        /// 获取视频设备分辨率列表
        /// </summary>
        /// <param name="ffmpegPath">FFmpeg路径</param>
        /// <param name="deviceName">视频设备名称</param>
        /// <returns>视频设备分辨率列表</returns>
        // ReSharper disable once IdentifierTypo
        public static Tuple<bool, List<VideoOption>, string> GetVideoResolutionList(string ffmpegPath,
            string deviceName)
        {
            var workingDirectory = Path.GetDirectoryName(ffmpegPath) ?? string.Empty;
            var cmd = $"-list_options true -f dshow -i video=\"{deviceName}\"";
            var ret = ProcessHelper.Execute(
                ffmpegPath,
                workingDirectory,
                cmd,
                Encoding.UTF8,
                redirectStandardOutput: false,
                redirectStandardError: true,
                getOutputStream: false,
                getErrorStream: true,
                createNoWindow: true
            );
            if (!ret.Item1)
            {
                return new Tuple<bool, List<VideoOption>, string>(false, new List<VideoOption>(), ret.Item3);
            }

            try
            {
                //[dshow @ 00000259a63dc680] DirectShow video device options(from video devices)
                //[dshow @ 00000259a63dc680]  Pin "捕获" (alternative pin name "捕获")
                //[dshow @ 00000259a63dc680]   vcodec=mjpeg min s=1280x720 fps = 30 max s = 1280x720 fps = 30
                //[dshow @ 00000259a63dc680]   vcodec=mjpeg min s=800x600 fps = 30 max s = 800x600 fps = 30
                //[dshow @ 00000259a63dc680]   vcodec=mjpeg min s=640x480 fps = 30 max s = 640x480 fps = 30
                //[dshow @ 00000259a63dc680]   vcodec=mjpeg min s=352x288 fps = 30 max s = 352x288 fps = 30
                //[dshow @ 00000259a63dc680]   vcodec=mjpeg min s=320x240 fps = 30 max s = 320x240 fps = 30
                //[dshow @ 00000259a63dc680]   vcodec=mjpeg min s=176x144 fps = 30 max s = 176x144 fps = 30
                //[dshow @ 00000259a63dc680]   vcodec=mjpeg min s=160x120 fps = 30 max s = 160x120 fps = 30
                //[dshow @ 00000259a63dc680]   pixel_format=yuyv422 min s=640x480 fps = 30 max s = 640x480 fps = 30
                //[dshow @ 00000259a63dc680]   pixel_format=yuyv422 min s=1280x720 fps = 10 max s = 1280x720 fps = 10
                //[dshow @ 00000259a63dc680]   pixel_format=yuyv422 min s=800x600 fps = 20 max s = 800x600 fps = 20
                //[dshow @ 00000259a63dc680]   pixel_format=yuyv422 min s=352x288 fps = 30 max s = 352x288 fps = 30
                //[dshow @ 00000259a63dc680]   pixel_format=yuyv422 min s=320x240 fps = 30 max s = 320x240 fps = 30
                //[dshow @ 00000259a63dc680]   pixel_format=yuyv422 min s=176x144 fps = 30 max s = 176x144 fps = 30
                //[dshow @ 00000259a63dc680]   pixel_format=yuyv422 min s=160x120 fps = 30 max s = 160x120 fps = 30
                var deviceList = ret.Item2.Item2
                    .Where(t => t.StartsWith("[dshow") && t.Contains("pixel_format="))
                    .Select(t =>
                    {
                        try
                        {
                            var value = "pixel_format=";
                            var index = t.IndexOf(value, StringComparison.CurrentCulture); //pixel_format=索引
                            var text = t.Substring(index +
                                                   value
                                                       .Length); //yuyv422 min s=640x480 fps = 30 max s = 640x480 fps = 30
                            index = text.IndexOf(" ", StringComparison.CurrentCulture);
                            var pixelFormat = text.Substring(0, index).TrimEnd();

                            value = "max s";
                            index = text.IndexOf(value, StringComparison.CurrentCulture); //max s索引
                            text = text.Substring(index + value.Length); // = 640x480 fps = 30
                            index = text.IndexOf("=", StringComparison.CurrentCulture); //=索引(max s=索引)
                            text = text.Substring(index + 1); //640x480 fps = 30
                            index = text.IndexOf("fps", StringComparison.CurrentCulture); //fps索引
                            var resolution = text.Substring(0, index).TrimStart().TrimEnd();

                            index = text.IndexOf("=", StringComparison.CurrentCulture); //=索引(fps =索引)
                            var fpsString = text.Substring(index + 1);
                            var fps = int.Parse(fpsString);

                            return new VideoOption()
                            {
                                Fps = fps,
                                PixelFormat = pixelFormat,
                                Resolution = resolution
                            };
                        }
                        catch
                        {
                            //Do nothing
                        }

                        return new VideoOption();
                    })
                    .ToList();
                var result = deviceList
                    .Where(t => t.Width > 0 && t.Height > 0)
                    .OrderByDescending(t => t.Width)
                    .ToList();
                return new Tuple<bool, List<VideoOption>, string>(true, result, string.Empty);
            }
            catch (Exception e)
            {
                return new Tuple<bool, List<VideoOption>, string>(false, new List<VideoOption>(), e.Message);
            }
        }

        /// <summary>
        /// 获取视频编码器列表
        /// </summary>
        /// <param name="ffmpegPath">FFmpeg路径</param>
        /// <returns>视频编码器列表</returns>
        // ReSharper disable once IdentifierTypo
        public static Tuple<bool, List<EncoderInfo>, string> GetEncoderList(string ffmpegPath)
        {
            var workingDirectory = Path.GetDirectoryName(ffmpegPath) ?? string.Empty;
            var cmd = "configure -encoders";
            var ret = ProcessHelper.Execute(
                ffmpegPath,
                workingDirectory,
                cmd,
                Encoding.UTF8,
                redirectStandardOutput: true,
                redirectStandardError: false,
                getOutputStream: true,
                getErrorStream: false,
                createNoWindow: false
            );
            if (!ret.Item1)
            {
                return new Tuple<bool, List<EncoderInfo>, string>(false, new List<EncoderInfo>(), ret.Item3);
            }

            try
            {
                //V....D libx264 libx264 H.264 / AVC / MPEG - 4 AVC / MPEG - 4 part 10(codec h264)
                //V....D libx264rgb libx264 H.264 / AVC / MPEG - 4 AVC / MPEG - 4 part 10 RGB(codec h264)
                //V....D libopenh264 OpenH264 H.264 / AVC / MPEG - 4 AVC / MPEG - 4 part 10(codec h264)
                //V....D h264_amf AMD AMF H.264 Encoder(codec h264)
                //V....D h264_mf H264 via MediaFoundation(codec h264)
                //V....D h264_nvenc NVIDIA NVENC H.264 encoder(codec h264)
                //V..... h264_qsv H.264 / AVC / MPEG - 4 AVC / MPEG - 4 part 10(Intel Quick Sync Video acceleration)(codec h264)
                //V....D libx265 libx265 H.265 / HEVC(codec hevc)
                //V....D hevc_amf AMD AMF HEVC encoder(codec hevc)
                //V....D hevc_mf HEVC via MediaFoundation(codec hevc)
                //V..... hevc_qsv HEVC(Intel Quick Sync Video acceleration) (codec hevc)
                //V....D libkvazaar libkvazaar H.265 / HEVC(codec hevc)
                var encoderList = ret.Item2.Item1
                    .Select(t => t.Trim())
                    .Where(t => t.StartsWith('V') && (t.Contains("264") || t.Contains("265") || t.Contains("HEVC")))
                    .Select(t => new EncoderInfo() { EncoderString = t })
                    .ToList();
                encoderList = encoderList.Where(t => !string.IsNullOrWhiteSpace(t.EncoderName)).ToList();
                return new Tuple<bool, List<EncoderInfo>, string>(true, encoderList, string.Empty);
            }
            catch (Exception e)
            {
                return new Tuple<bool, List<EncoderInfo>, string>(false, new List<EncoderInfo>(), e.Message);
            }
        }

        /// <summary>
        /// 获取硬件加速方法列表
        /// </summary>
        /// <param name="ffmpegPath">FFmpeg路径</param>
        /// <returns>操作结果</returns>
        public static Tuple<bool, List<string>, string> GetHardwareAccelerationMethodList(string ffmpegPath)
        {
            var methodList = new List<string>();
            var workingDirectory = Path.GetDirectoryName(ffmpegPath) ?? string.Empty;
            var cmd = "-hwaccels"; //ffmpeg -hwaccels
            var ret = ProcessHelper.Execute(
                ffmpegPath,
                workingDirectory,
                cmd,
                Encoding.UTF8,
                redirectStandardOutput: true,
                redirectStandardError: true,
                getOutputStream: true,
                getErrorStream: true,
                createNoWindow: true
            );
            if (!ret.Item1)
            {
                return Tuple.Create(false, methodList, ret.Item3);
            }

            //Hardware acceleration methods:
            //cuda
            //dxva2
            //qsv
            //d3d11va
            //opencl
            //vulkan
            var methodsDescription = "Hardware acceleration methods:";
            var list = ret.Item2.Item1; //standardList
            var index = list.FindIndex(t => t.StartsWith(methodsDescription));
            if (index < 0)
            {
                return Tuple.Create(false, methodList, @"FFmpeg没有发现硬件加速方法");
            }

            for (var i = index + 1; i < list.Count; i++)
            {
                methodList.Add(list[i]);
            }

            return Tuple.Create(true, methodList, string.Empty);
        }

        public static Tuple<bool, string> StartRecord(
            // ReSharper disable once IdentifierTypo
            string ffmpegPath,
            string camera,
            string mic,
            string resolution,
            string encoder,
            string videoPath,
            int fps = 0,
            string hardwareAccelerationMethod = ""
        )
        {
            var directory = string.IsNullOrWhiteSpace(videoPath)
                ? Path.GetDirectoryName(ffmpegPath) ?? string.Empty
                : videoPath;
            VideoPath = Path.Combine(directory, $"{DateTime.Now:yyyyMMdd_HHmmss}.mp4");

            var acceleration = string.IsNullOrWhiteSpace(hardwareAccelerationMethod)
                ? string.Empty
                : $"-hwaccel {hardwareAccelerationMethod}";
            var cmd = $"{acceleration} -f dshow -i video=\"{camera}\":audio=\"{mic}\"";
            if (!string.IsNullOrWhiteSpace(encoder))
            {
                cmd = $"{cmd} -c:v {encoder}";
            }

            if (!string.IsNullOrWhiteSpace(resolution))
            {
                cmd = $"{cmd} -s {resolution}";
            }

            if (fps > 0)
            {
                cmd = $"{cmd} -r {fps}";
            }

            cmd = $"{cmd} {VideoPath}";

            return StartRecord(ffmpegPath, directory, cmd);
        }

        public static Tuple<bool, string> StartScreenRecord(
            // ReSharper disable once IdentifierTypo
            string ffmpegPath,
            string mic,
            string encoder,
            string videoPath,
            int fps = 0,
            string hardwareAccelerationMethod = ""
        )
        {
            var directory = string.IsNullOrWhiteSpace(videoPath)
                ? Path.GetDirectoryName(ffmpegPath) ?? string.Empty
                : videoPath;
            VideoPath = Path.Combine(directory, $"{DateTime.Now:yyyyMMdd_HHmmss}.mp4");

            var acceleration = string.IsNullOrWhiteSpace(hardwareAccelerationMethod)
                ? string.Empty
                : $"-hwaccel {hardwareAccelerationMethod}";
            var cmd = $"{acceleration} -f dshow -i audio=\"{mic}\" -f gdigrab -i desktop";
            if (!string.IsNullOrWhiteSpace(encoder))
            {
                cmd = $"{cmd} -vcodec {encoder}";
            }

            if (fps > 0)
            {
                cmd = $"{cmd} -r {fps}";
            }

            cmd = $"{cmd} -pix_fmt yuv420p {VideoPath}";

            return StartRecord(ffmpegPath, directory, cmd);
        }

        private static Tuple<bool, string> StartRecord(
            // ReSharper disable once IdentifierTypo
            string ffmpegPath,
            string workingDirectory,
            string cmd
        )
        {
            try
            {
                //启动主进程
                _process = new Process
                {
                    StartInfo =
                    {
                        UseShellExecute = false, //不使用系统Shell启动
                        FileName = ffmpegPath,
                        WorkingDirectory = workingDirectory,
                        RedirectStandardInput = true,
                        Arguments = cmd, //参数
                        CreateNoWindow = true //不显示窗口
                    }
                };
                _process.Start();
                return new Tuple<bool, string>(true, string.Empty);
            }
            catch (Exception e)
            {
                return new Tuple<bool, string>(false, e.Message);
            }
        }

        public static void StopRecord()
        {
            if (_process is { HasExited: false })
            {
                _process.StandardInput.WriteLine("q");
                _process.WaitForExit();
                _process.Close();
            }

            _process?.Dispose();
            _process = null;
        }
    }
}
